﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Framework.Core.BootStrapper;
using Framework.Core.CommandLine;
using Framework.Core.Dependency.Adapter;
using Framework.Core.Dependency.Domain;
using Framework.Core.Dependency.Parser;
using Framework.Core.Dependency.Visitor;
using log4net;

namespace Framework.Core.Dependency
{
    /// <summary>
    ///     @sample : /p:DependencyWalker /r:D:\dev\allSources /b:"Main\Source" /lp:"C:\Program Files (x86)\Microsoft Visual Studio 10.0\,D:\_Tools\_Sandbox\Build\LegacyBin,D:\_Tools\_Sandbox\Build\ToNugetPackage,c:\Program Files\Common Files\system\ado,c:\WINDOWS\system32,C:\HOMEWARE\CTY_SDK\Reference Binaries,C:\WINDOWS\Microsoft.NET\Framework,D:\dev\allSources\CTY.RAD\Main\Source|*.h"
    /// </summary>
    // ReSharper disable ClassNeverInstantiated.Global
    internal class DependencyWalkerArgs : PluginCommandLineArgs
    // ReSharper restore ClassNeverInstantiated.Global
    {
        [CommandLineArgs(
            ArgumentRequirements = ArgumentRequirements.Required,
            Name = "Branch",
            ShortName = "b"
            )]
        // ReSharper disable UnusedAutoPropertyAccessor.Global
        public string Branch { get; set; }

        [CommandLineArgs(
            ArgumentRequirements = ArgumentRequirements.Required,
            Name = "Root",
            ShortName = "r"
            )]
        // ReSharper disable UnusedAutoPropertyAccessor.Global
        public string Root { get; set; }

        [CommandLineArgs(
            ArgumentRequirements = ArgumentRequirements.Required,
            Name = "LibsPaths",
            ShortName = "lp"
            )]
        // ReSharper disable UnusedAutoPropertyAccessor.Global
        public string LibsPaths { get; set; }
    }

    // ReSharper disable UnusedMember.Global
    internal class DependencyWalker : PluginBase<DependencyWalkerArgs>
    // ReSharper restore UnusedMember.Global
    {
        private static readonly ILog Logger = LogManager.GetLogger(typeof(DependencyWalker));
        private readonly IDirectoryParser<Solution> campainParser;
        private readonly IDirectoryParser<Solution> sourceParser;

        public DependencyWalker()
            : this(new SourceParser(), new CampainParser())
        {
        }

        internal DependencyWalker(IDirectoryParser<Solution> sourceParser, IDirectoryParser<Solution> campainParser)
        {
            this.sourceParser = sourceParser;
            this.campainParser = campainParser;
        }

        public override int Execute(DependencyWalkerArgs args)
        {
            var campainSolutions = AnalyseCampain(args);

            var solutions = ParseSolutions(args);

            var analyzedSolution = AnalyseDependencies(solutions.ToList(), campainSolutions, args.LibsPaths.Split(','), args.Root).ToList();

            const string analyzedsolutionXml = @"c:\analyzedSolution.xml";
            analyzedSolution.Serialize(analyzedsolutionXml);

            var slns = analyzedsolutionXml.Deserialize<Solution>();
            return 0;
        }

        private static IEnumerable<Solution> AnalyseDependencies(IEnumerable<Solution> solutions, ICollection<Solution> campainSolutions, IEnumerable<string> libsPaths, string root)
        {
            Logger.Info("Analyse Dependencies");

            var solutionToAnalyse = solutions.Intersect(campainSolutions).OrderBy(s=>s.Name).ToList();
            
            var expectedSolutions = campainSolutions.Where(s => s.Extension != ".Wix" && s.Extension != ".dsw").Distinct().ToList();

            expectedSolutions.Except(solutionToAnalyse).Serialize(@"c:\solutionsNotFound.xml");

            var expectedCount = expectedSolutions.Count();
            
            if (solutionToAnalyse.Count() != expectedCount)
            {
                throw new ArgumentException(string.Format("Expected {0} solutions actual {1}", expectedCount, solutionToAnalyse.Count));
            }
            
            Logger.InfoFormat("Analysing {0} solutions", expectedCount);

            var projectsFromSolutions = solutionToAnalyse.SelectMany(x => x.Projects).ToList();

            var dependencyVisitor = new DependencyVisitor(projectsFromSolutions, GetReferenceBinaries(libsPaths));
            var visitors = new List<IVisitor<Project>> { new ComExpositionVisitor(), new UnitTestVisitor(campainSolutions), dependencyVisitor };

            Parallel.ForEach(projectsFromSolutions,
                p =>
                {
                    foreach (var visitor in visitors)
                    {
                        p.Accept(visitor);
                    }
                });

            dependencyVisitor.ModulesNotFound.Serialize(@"c:\modulesNotFound.xml");

            var modulesFound = FindModulesNotFound(dependencyVisitor.ModulesNotFound, root);

            var remain = dependencyVisitor.ModulesNotFound.Except(modulesFound).ToList();
            remain.Serialize(@"c:\modulesNotFoundRemaining.xml");

            return solutionToAnalyse;
        }

        private static IEnumerable<Module> FindModulesNotFound(IEnumerable<Module> modulesNotFound, string root)
        {
            var modulesFound = new List<Module>();

            var files = GetFiles(root);

            foreach (var module in modulesNotFound)
            {
                var candidate = files.FirstOrDefault(f => string.Equals(Path.GetFileName(f), Path.GetFileName(module.Path), StringComparison.InvariantCultureIgnoreCase));

                if (candidate != null)
                {
                    modulesFound.Add(module);
                    var destFileName = Path.Combine(@"D:\_Tools\_Sandbox\Build\Missings\", Path.GetFileName(candidate));

                    if (!File.Exists(destFileName))
                    {
                        File.Copy(candidate, destFileName);
                    }
                }
            }

            return modulesFound;
        }

        private static List<string> GetFiles(string root)
        {
            var directories = new List<string>
                                  {
                                      root,
                                      @"c:\sg\meteor\bin",
                                      @"C:\Program Files (x86)\Gallio\bin",
                                  };

            var files = new List<string>();

            foreach (var dir in directories)
            {
                files.AddRange(Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories));
            }
            return files;
        }

        private static List<string> GetReferenceBinaries(IEnumerable<string> libsPaths)
        {
            var referenceBinaries = new List<string>();

            foreach (var libsPath in libsPaths)
            {
                var criteria = libsPath.Split('|');

                referenceBinaries.AddRange(Directory.GetFiles(criteria[0], (criteria.Count() == 2) ? criteria[1] : "*.*", SearchOption.AllDirectories));
            }

            return referenceBinaries;
        }

        private IEnumerable<Solution> ParseSolutions(DependencyWalkerArgs commandLines)
        {
            Logger.Info("Parse Solution");
            IList<Solution> solutions = sourceParser.Parse(commandLines.Root, commandLines.Branch).ToList();

            solutions.Serialize(@"c:\solutions.xml");
            return solutions;
        }

        private IList<Solution> AnalyseCampain(DependencyWalkerArgs commandLines)
        {
            Logger.Info("Analyse Campain");
            var campainSolutions = campainParser.Parse(commandLines.Root, commandLines.Branch).ToList();

            campainSolutions.Serialize(@"c:\campains.xml");
            return campainSolutions;
        }
    }
}